﻿using UnityEngine;
using System.Collections;

// Require a character controller to be attached to the same game object
[RequireComponent (typeof(CharacterController))]
[AddComponentMenu ("Character/Character Motor")]
public class CharacterMotor: MonoBehaviour
{
	
	// Does this script currently respond to input?
	public bool canControl = true;

    public bool useFixedUpdate = true;

	// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view.
	// Very handy for organization!

	// The current global direction we want the character to move in.
	public Vector3 m_InputMoveDirection = Vector3.zero;

	// Is the jump button held down? We use this interface instead of checking
	// for the jump button directly so this script can also be used by AIs.
	public bool m_InputJump = false;

	public class CharacterMotorMovement 
	{
		// The maximum horizontal speed when moving
		public float m_MaxForwardSpeed  = 200.0f;
		public float m_MaxSidewaysSpeed = 100.0f;
		public float m_MaxBackwardsSpeed = 200.0f;
		
		// Curve for multiplying speed based on slope (negative = downwards)
		public AnimationCurve m_SlopeSpeedMultiplier = new AnimationCurve( new Keyframe(-90, 1), new Keyframe(0, 1), new Keyframe(90, 0));
		
		// How fast does the character change speeds?  Higher is faster.
		public float m_MaxGroundAcceleration = 30.0f;
		public float m_MaxAirAcceleration = 20.0f;
		
		// The gravity for the character
		public float m_Gravity = 10.0f;
		public float m_MaxFallSpeed = 20.0f;
		
		// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view.
		// Very handy for organization!
		
		// The last collision flags returned from controller.Move
		public CollisionFlags m_CollisionFlags; 
		
		// We will keep track of the character's current velocity,
		public Vector3 m_Velocity;
		
		// This keeps track of our current velocity while we're not grounded
		public Vector3 m_FrameVelocity = Vector3.zero;
		public Vector3 m_HitPoint = Vector3.zero;
		public Vector3 m_LastHitPoint = new Vector3(Mathf.Infinity, 0, 0);
	}

	public CharacterMotorMovement m_Movement = new CharacterMotorMovement();

	public enum MovementTransferOnJump 
	{
		None, // The jump is not affected by velocity of floor at all.
		InitTransfer, // Jump gets its initial velocity from the floor, then gradualy comes to a stop.
		PermaTransfer, // Jump gets its initial velocity from the floor, and keeps that velocity until landing.
		PermaLocked // Jump is relative to the movement of the last touched floor and will move together with that floor.
	}

	// We will contain all the jumping related variables in one helper class for clarity.
	public class CharacterMotorJumping 
	{
		// Can the character jump?
		 public bool m_Enabled = true;
		
		// How high do we jump when pressing jump and letting go immediately
		public float m_BaseHeight = 1.0f;
		
		// We add extraHeight units (meters) on top when holding the button down longer while jumping
		public float m_ExtraHeight = 4.1f;
		
		// How much does the character jump out perpendicular to the surface on walkable surfaces?
		// 0 means a fully vertical jump and 1 means fully perpendicular.
		public float m_PerpAmount = 0.0f;
		
		// How much does the character jump out perpendicular to the surface on too steep surfaces?
		// 0 means a fully vertical jump and 1 means fully perpendicular.
		public float m_SteepPerpAmount  = 0.5f;
		
		// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view.
		// Very handy for organization!
		
		// Are we jumping? (Initiated with jump button and not grounded yet)
		// To see if we are just in the air (initiated by jumping OR falling) see the grounded variable.
		public bool m_Jumping = false;
		

		public bool m_HoldingJumpButton = false;
		
		// the time we jumped at (Used to determine for how long to apply extra jump power after jumping.)
		public float m_LastStartTime = 0.0f;

		public float m_LastButtonDownTime = -100;
		
		public Vector3 m_JumpDir = Vector3.up;
	}

	public CharacterMotorJumping  m_Jumping = new CharacterMotorJumping();

	public class CharacterMotorMovingPlatform 
	{
		public bool m_Enabled  = true;
		
		public MovementTransferOnJump  m_MovementTransfer = MovementTransferOnJump.PermaTransfer;
		
		public Transform m_HitPlatform;
		
		public Transform m_ActivePlatform;
		
		public Vector3 m_ActiveLocalPoint;
		
		public Vector3 m_ActiveGlobalPoint;
		
		public Quaternion m_ActiveLocalRotation;
		
		public Quaternion m_ActiveGlobalRotation;
		
		public Matrix4x4 m_LastMatrix;
		
		public Vector3 m_PlatformVelocity ;
		
		public bool m_NewPlatform;
	}

	public CharacterMotorMovingPlatform  m_MovingPlatform = new CharacterMotorMovingPlatform();

	public class CharacterMotorSliding 
	{
		// Does the character slide on too steep surfaces?
		public bool m_Enabled = true;

		// How fast does the character slide on steep surfaces?
		public float m_SlidingSpeed = 15;

		// How much can the player control the sliding direction?
		// If the value is 0.5 the player can slide sideways with half the speed of the downwards sliding speed.
		public float m_SidewaysControl = 1.0f;

		// How much can the player influence the sliding speed?
		// If the value is 0.5 the player can speed the sliding up to 150% or slow it down to 50%.
		public float m_SpeedControl = 0.4f;
	}

	public CharacterMotorSliding m_Sliding = new CharacterMotorSliding();
	public bool m_Grounded = true;
	public Vector3 m_GroundNormal = Vector3.zero;

	private Vector3 m_LastGroundNormal = Vector3.zero;
	private Transform  m_Tr;
	private CharacterController m_Controller;

	void Awake () 
	{
		m_Controller = GetComponent<CharacterController>();
		m_Tr = transform;
	}

  void UpdateFunction () 
  {
	// We copy the actual velocity into a temporary variable that we can manipulate.
	Vector3 m_Velocity = m_Movement.m_Velocity; 
	
	// Update velocity based on input
	m_Velocity = ApplyInputVelocityChange(m_Velocity);
	
	// Apply gravity and jumping force
	m_Velocity = ApplyGravityAndJumping (m_Velocity);
	
	// Moving platform support
	Vector3 m_MoveDistance = Vector3.zero;
	if (MoveWithPlatform()) 
	{
		Vector3	m_NewGlobalPoint= m_MovingPlatform.m_ActivePlatform.TransformPoint(m_MovingPlatform.m_ActiveLocalPoint);  
		m_MoveDistance = (m_NewGlobalPoint - m_MovingPlatform.m_ActiveGlobalPoint);   
		if (m_MoveDistance != Vector3.zero)
	
		m_Controller.Move(m_MoveDistance); 
		 
		// Support moving platform rotation as well:
		Quaternion m_NewGlobalRotation = m_MovingPlatform.m_ActivePlatform.rotation * m_MovingPlatform.m_ActiveLocalRotation;
		Quaternion m_RotationDiff = m_NewGlobalRotation * Quaternion.Inverse(m_MovingPlatform.m_ActiveGlobalRotation);  
		
	    float m_YRotation = m_RotationDiff.eulerAngles.y; 
   		if ( m_YRotation != 0)  
		{
			// Prevent rotation of the local up vector
		 	 m_Tr.Rotate (0, m_YRotation, 0, 0); 
		}
	}
	
	// Save lastPosition for velocity calculation.
		Vector3 m_LastPosition = m_Tr.position;
	
	// We always want the movement to be framerate independent.  Multiplying by Time.deltaTime does this.
	Vector3 m_CurrentMovementOffset  = m_Velocity * Time.deltaTime;
	
	// Find out how much we need to push towards the ground to avoid loosing grouning
	// when walking down a step or over a sharp change in slope.
	float m_PushDownOffset = Mathf.Max(m_Controller.stepOffset, (new Vector3(m_CurrentMovementOffset.x, 0, m_CurrentMovementOffset.z).magnitude));  
	if (m_Grounded)
		m_CurrentMovementOffset -= m_PushDownOffset * Vector3.up;
	
	// Reset variables that will be set by collision function
	m_MovingPlatform.m_HitPlatform = null; 
	m_GroundNormal = Vector3.zero;
	
	// Move our character!
	m_Movement.m_CollisionFlags = m_Controller.Move (m_CurrentMovementOffset); 
	
	m_Movement.m_LastHitPoint = m_Movement.m_HitPoint; 
	m_LastGroundNormal = m_GroundNormal; 
	
	if (m_MovingPlatform.m_Enabled && m_MovingPlatform.m_ActivePlatform != m_MovingPlatform.m_HitPlatform) 
	{
			if (m_MovingPlatform.m_HitPlatform != null) 
			{
				m_MovingPlatform.m_ActivePlatform = m_MovingPlatform.m_HitPlatform; 
				m_MovingPlatform.m_LastMatrix = m_MovingPlatform.m_HitPlatform.localToWorldMatrix; 
				m_MovingPlatform.m_NewPlatform = true; 
			}
	}
	
	// Calculate the velocity based on the current and previous position.  
	// This means our velocity will only be the amount the character actually moved as a result of collisions.
	Vector3 m_OldHVelocity = new Vector3(m_Velocity.x, 0, m_Velocity.z);  
	m_Movement.m_Velocity = (m_Tr.position - m_LastPosition) / Time.deltaTime; 
	Vector3 m_NewHVelocity = new Vector3(m_Movement.m_Velocity.x, 0, m_Movement.m_Velocity.z);  
	 
	// The CharacterController can be moved in unwanted directions when colliding with things.
	// We want to prevent this from influencing the recorded velocity.
	if (m_OldHVelocity == Vector3.zero) 
	{
		m_Movement.m_Velocity = new Vector3(0, m_Movement.m_Velocity.y, 0); 
	}
	else 
	{
		float m_ProjectedNewVelocity = Vector3.Dot(m_NewHVelocity, m_OldHVelocity) / m_OldHVelocity.sqrMagnitude; 
		m_Movement.m_Velocity = m_OldHVelocity * Mathf.Clamp01(m_ProjectedNewVelocity) + m_Movement.m_Velocity.y * Vector3.up; 
	}
	
	if (m_Movement.m_Velocity.y < m_Velocity.y - 0.001) { 
			if (m_Movement.m_Velocity.y < 0) 
			{
				// Something is forcing the CharacterController down faster than it should.
				// Ignore this
				m_Movement.m_Velocity.y = m_Velocity.y; 
			}
		else 
		{
			// The upwards movement of the CharacterController has been blocked.
			// This is treated like a ceiling collision - stop further jumping here.
			m_Jumping.m_HoldingJumpButton = false;  
		}
	}
	
	// We were grounded but just loosed grounding
	if (m_Grounded && !IsGroundedTest()) 
	{    
		m_Grounded = false; 
		
		// Apply inertia from platform
		if (m_MovingPlatform.m_Enabled && 
		    (m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.InitTransfer ||  
			m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.PermaTransfer)
		    ) 
		{
				m_Movement.m_FrameVelocity = m_MovingPlatform.m_PlatformVelocity; 
				m_Movement.m_Velocity += m_MovingPlatform.m_PlatformVelocity;
		}
		
			SendMessage("OnFall", SendMessageOptions.DontRequireReceiver);
			// We pushed the character down to ensure it would stay on the ground if there was any.
			// But there wasn't so now we cancel the downwards offset to make the fall smoother.
			m_Tr.position += m_PushDownOffset * Vector3.up; 
	}
	// We were not grounded but just landed on something
      else if (!m_Grounded && IsGroundedTest()) {   
			m_Grounded = true;
			m_Jumping.m_Jumping = false;  
			StartCoroutine(SubtractNewPlatformVelocity());
		
			SendMessage("OnLand", SendMessageOptions.DontRequireReceiver);
		}
	
	// Moving platforms support
		if (MoveWithPlatform()) { 
			// Use the center of the lower half sphere of the capsule as reference point.
			// This works best when the character is standing on moving tilting platforms. 
			m_MovingPlatform.m_ActiveGlobalPoint = m_Tr.position + Vector3.up * (m_Controller.center.y - m_Controller.height*0.5f + m_Controller.radius); 
			m_MovingPlatform.m_ActiveLocalPoint = m_MovingPlatform.m_ActivePlatform.InverseTransformPoint(m_MovingPlatform.m_ActiveGlobalPoint);
		
			// Support moving platform rotation as well:
			m_MovingPlatform.m_ActiveGlobalRotation = m_Tr.rotation;
			m_MovingPlatform.m_ActiveLocalRotation = Quaternion.Inverse(m_MovingPlatform.m_ActivePlatform.rotation) * m_MovingPlatform.m_ActiveGlobalRotation;
		} 
	}
	
	void FixedUpdate () 
	{ 
		if (m_MovingPlatform.m_Enabled) 
		{
			if (m_MovingPlatform.m_ActivePlatform != null) 
			{ 
				if (!m_MovingPlatform.m_NewPlatform) 
				{ 
					Vector3 m_LastVelocity = m_MovingPlatform.m_PlatformVelocity; 
				
					m_MovingPlatform.m_PlatformVelocity = ( 
					m_MovingPlatform.m_ActivePlatform.localToWorldMatrix.MultiplyPoint3x4(m_MovingPlatform.m_ActiveLocalPoint)
					- m_MovingPlatform.m_LastMatrix.MultiplyPoint3x4(m_MovingPlatform.m_ActiveLocalPoint)
					) / Time.deltaTime;
				}
				m_MovingPlatform.m_LastMatrix = m_MovingPlatform.m_ActivePlatform.localToWorldMatrix; 
				m_MovingPlatform.m_NewPlatform = false; 
			}
			else 
			{
				m_MovingPlatform.m_PlatformVelocity = Vector3.zero; 	
			}
		}
	
	if (useFixedUpdate) 
		UpdateFunction();
	}

	void Update () 
	{
		if (!useFixedUpdate)
		UpdateFunction();
	}

	Vector3 ApplyInputVelocityChange (Vector3 _Velocity) 
	{	  
		if (!canControl)
		m_InputMoveDirection = Vector3.zero; 
	
		// Find desired velocity
		Vector3 desiredVelocity;
		if (m_Grounded && TooSteep()) 
		{ 
			// The direction we're sliding in
			desiredVelocity = new Vector3(m_GroundNormal.x, 0, m_GroundNormal.z).normalized;   
			// Find the input movement direction projected onto the sliding direction
			Vector3 m_ProjectedMoveDir = Vector3.Project(m_InputMoveDirection, desiredVelocity);
			// Add the sliding direction, the spped control, and the sideways control vectors
			desiredVelocity = desiredVelocity + m_ProjectedMoveDir * m_Sliding.m_SpeedControl + (m_InputMoveDirection - m_ProjectedMoveDir) * m_Sliding.m_SidewaysControl;      
			// Multiply with the sliding speed
			desiredVelocity *= m_Sliding.m_SlidingSpeed;  
		}
		else
			desiredVelocity = GetDesiredHorizontalVelocity();    
	
		if (m_MovingPlatform.m_Enabled && m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.PermaTransfer) 
		{ 
			desiredVelocity += m_Movement.m_FrameVelocity;  
			desiredVelocity.y = 0;
		}
	
		if (m_Grounded)
			desiredVelocity = AdjustGroundVelocityToNormal(desiredVelocity, m_GroundNormal); 
		else
		_Velocity.y = 0; 
	
	// Enforce max velocity change 
		float maxVelocityChange = GetMaxAcceleration(m_Grounded) * Time.deltaTime;
		Vector3 velocityChangeVector = (desiredVelocity - _Velocity);  
		if (velocityChangeVector.sqrMagnitude > maxVelocityChange * maxVelocityChange) 
		{
			velocityChangeVector = velocityChangeVector.normalized * maxVelocityChange;
		}
		// If we're in the air and don't have control, don't apply any velocity change at all.
		// If we're on the ground and don't have control we do apply it - it will correspond to friction.
		if (m_Grounded || canControl)  
		_Velocity += velocityChangeVector; 
	
		if (m_Grounded) 
		{ 
			// When going uphill, the CharacterController will automatically move up by the needed amount.
			// Not moving it upwards manually prevent risk of lifting off from the ground.
			// When going downhill, DO move down manually, as gravity is not enough on steep hills.
			_Velocity.y = Mathf.Min(_Velocity.y, 0); 
		}
	
		return _Velocity; 
	}
	
	Vector3 ApplyGravityAndJumping (Vector3 _Velocity) 
	{
	
		if (!m_InputJump || !canControl)
		{ 
			m_Jumping.m_HoldingJumpButton = false; 
			m_Jumping.m_LastButtonDownTime = -100; 
		}
	
		if (m_InputJump && m_Jumping.m_LastButtonDownTime < 0 && canControl) 
			m_Jumping.m_LastButtonDownTime = Time.time; 
	
		if (m_Grounded) 
			_Velocity.y = Mathf.Min(0, _Velocity.y) - m_Movement.m_Gravity * Time.deltaTime;  
		else
			_Velocity.y = m_Movement.m_Velocity.y - m_Movement.m_Gravity * Time.deltaTime;   
		
			// When jumping up we don't apply gravity for some time when the user is holding the jump button.
			// This gives more control over jump height by pressing the button longer.
		if (m_Jumping.m_Jumping && m_Jumping.m_HoldingJumpButton) 
		{ 
			// Calculate the duration that the extra jump force should have effect.
			// If we're still less than that duration after the jumping time, apply the force.
			if (Time.time < m_Jumping.m_LastStartTime + m_Jumping.m_ExtraHeight / CalculateJumpVerticalSpeed(m_Jumping.m_BaseHeight)) 
			{  
				// Negate the gravity we just applied, except we push in jumpDir rather than jump upwards.
				_Velocity += m_Jumping.m_JumpDir * m_Movement.m_Gravity * Time.deltaTime; 
			}
		}
		
		// Make sure we don't fall any faster than maxFallSpeed. This gives our character a terminal velocity.
		_Velocity.y = Mathf.Max (_Velocity.y, -m_Movement.m_MaxFallSpeed); 
	
	
		if (m_Grounded) 
		{
			// Jump only if the jump button was pressed down in the last 0.2 seconds.
			// We use this check instead of checking if it's pressed down right now
			// because players will often try to jump in the exact moment when hitting the ground after a jump
			// and if they hit the button a fraction of a second too soon and no new jump happens as a consequence,
			// it's confusing and it feels like the game is buggy.
			if (m_Jumping.m_Enabled && canControl && (Time.time - m_Jumping.m_LastButtonDownTime < 0.2)) 
			{ 
				m_Grounded = false;
				m_Jumping.m_Jumping = true; 
				m_Jumping.m_LastStartTime = Time.time; 
				m_Jumping.m_LastButtonDownTime = -100; 
				m_Jumping.m_HoldingJumpButton = true; 
			
				// Calculate the jumping direction
				if (TooSteep())
					m_Jumping.m_JumpDir = Vector3.Slerp(Vector3.up, m_GroundNormal, m_Jumping.m_SteepPerpAmount); 
				else
					m_Jumping.m_JumpDir = Vector3.Slerp(Vector3.up, m_GroundNormal, m_Jumping.m_PerpAmount); 
			
			// Apply the jumping force to the velocity. Cancel any vertical velocity first.
			_Velocity.y = 0; 
			_Velocity += m_Jumping.m_JumpDir * CalculateJumpVerticalSpeed (m_Jumping.m_BaseHeight); 
			
			// Apply inertia from platform
				if (m_MovingPlatform.m_Enabled && 
			   (m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.InitTransfer ||
			 	m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.PermaTransfer)
			    ) 
				{
					m_Movement.m_FrameVelocity = m_MovingPlatform.m_PlatformVelocity; 
					_Velocity += m_MovingPlatform.m_PlatformVelocity;  
				} 
			
				SendMessage("OnJump", SendMessageOptions.DontRequireReceiver); 
			} 
			else 
			{
			m_Jumping.m_HoldingJumpButton = false; 
			}
		}
	
		return _Velocity; 
	}
	void OnControllerColliderHit(ControllerColliderHit _Hit)
	{
		if (_Hit.normal.y > 0 && _Hit.normal.y > m_GroundNormal.y && _Hit.moveDirection.y < 0) 
		{ 
			if ((_Hit.point - m_Movement.m_LastHitPoint).sqrMagnitude > 0.001 || m_LastGroundNormal == Vector3.zero) 
			{
				m_GroundNormal = _Hit.normal;
			}
			else
			{
				m_GroundNormal = m_LastGroundNormal; 
			
				m_MovingPlatform.m_HitPlatform = _Hit.collider.transform;    
				m_Movement.m_HitPoint = _Hit.point;      
				m_Movement.m_FrameVelocity = Vector3.zero;   
			}
		}
	}

 	IEnumerator SubtractNewPlatformVelocity () 
	{
		// When landing, subtract the velocity of the new ground from the character's velocity
		// since movement in ground is relative to the movement of the ground.
		if (m_MovingPlatform.m_Enabled && 
	    (m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.InitTransfer ||
	 	m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.PermaTransfer)
	    ) 
		{
			// If we landed on a new platform, we have to wait for two FixedUpdates
			// before we know the velocity of the platform under the character
			if (m_MovingPlatform.m_NewPlatform) 
			{ 
				Transform platform = m_MovingPlatform.m_ActivePlatform; 
				yield return new WaitForFixedUpdate(); 
				yield return new WaitForFixedUpdate(); 
				if (m_Grounded && platform == m_MovingPlatform.m_ActivePlatform) 
					yield return 1;
			}
			m_Movement.m_Velocity -= m_MovingPlatform.m_PlatformVelocity; 
		}
	}
	bool MoveWithPlatform () 
	{
	return (
		m_MovingPlatform.m_Enabled 
		&& (m_Grounded || m_MovingPlatform.m_MovementTransfer == MovementTransferOnJump.PermaLocked)
		&& m_MovingPlatform.m_ActivePlatform != null 
		);
	}
	Vector3 GetDesiredHorizontalVelocity () 
	{
		// Find desired velocity
		Vector3 desiredLocalDirection = m_Tr.InverseTransformDirection(m_InputMoveDirection); 
		float maxSpeed = MaxSpeedInDirection(desiredLocalDirection);
		if (m_Grounded) 
		{
			// Modify max speed on slopes based on slope speed multiplier curve
			float movementSlopeAngle = Mathf.Asin(m_Movement.m_Velocity.normalized.y)  * Mathf.Rad2Deg;
			maxSpeed *= m_Movement.m_SlopeSpeedMultiplier.Evaluate(movementSlopeAngle); 
		}
		return m_Tr.TransformDirection(desiredLocalDirection * maxSpeed);
	}

	Vector3 AdjustGroundVelocityToNormal(Vector3 _HVelocity ,Vector3 _GroundNormal)   
	{
		Vector3 sideways = Vector3.Cross(Vector3.up, _HVelocity);
		return Vector3.Cross(sideways, _GroundNormal).normalized * _HVelocity.magnitude;
	}

	bool IsGroundedTest () 
	{
		return (m_GroundNormal.y > 0.01);
	}

	float GetMaxAcceleration (bool _Grounded ) 
	{
		// Maximum acceleration on ground and in air
		if (_Grounded)
			return m_Movement.m_MaxGroundAcceleration;
		else
			return m_Movement.m_MaxAirAcceleration;
	}

	float CalculateJumpVerticalSpeed (float _TargetJumpHeight) 
	{
		// From the jump height and gravity we deduce the upwards speed 
		// for the character to reach at the apex.
		return Mathf.Sqrt (2 * _TargetJumpHeight * m_Movement.m_Gravity);
	}

	bool IsJumping () 
	{
		return m_Jumping.m_Jumping;
	}

	bool IsSliding () 
	{
		return (m_Grounded && m_Sliding.m_Enabled && TooSteep()); 
	}

	bool IsTouchingCeiling () 
	{
		return (m_Movement.m_CollisionFlags & CollisionFlags.CollidedAbove) != 0;
	}

	bool IsGrounded () 
	{ 
		return m_Grounded;
	}

    bool TooSteep () 
	{
		return (m_GroundNormal.y <= Mathf.Cos(m_Controller.slopeLimit * Mathf.Deg2Rad));
	}

	Vector3 GetDirection () 
	{
		return m_InputMoveDirection; 
	}

	void SetControllable (bool controllable) 
	{
		canControl = controllable;
	}

	// Project a direction onto elliptical quater segments based on forward, sideways, and backwards speed.
	// The function returns the length of the resulting vector.
	float MaxSpeedInDirection (Vector3 _DesiredMovementDirection )
	{
		if (_DesiredMovementDirection == Vector3.zero)
		return 0;
		else
		{
			float zAxisEllipseMultiplier = (_DesiredMovementDirection.z > 0 ? m_Movement.m_MaxForwardSpeed : m_Movement.m_MaxBackwardsSpeed) / m_Movement.m_MaxSidewaysSpeed;
			Vector3 temp = new Vector3(_DesiredMovementDirection.x, 0, _DesiredMovementDirection.z / zAxisEllipseMultiplier).normalized;
			float length = new Vector3(temp.x, 0, temp.z * zAxisEllipseMultiplier).magnitude * m_Movement.m_MaxSidewaysSpeed;
			return length;
		}
	}

	void SetVelocity (Vector3 velocity) 
	{
		m_Grounded = false;
		m_Movement.m_Velocity = velocity;
		m_Movement.m_FrameVelocity = Vector3.zero;
		SendMessage("OnExternalVelocity");
	}
}
